using System;
using System.Net;
using System.Threading;
using System.ComponentModel;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Net.Sockets;
using System.Net.Cache;
using BrainTechLLC.Communications;
using AcedUtils;
using System.Diagnostics;
using System.Windows.Forms;

namespace BrainTechLLC.Communications
{
  [Browsable(true)]
  public class WebCommunicationsLibrary : CommunicationsLibrary, ICommunications
  {
    static public bool fStopHosting = false;
    private HttpRequestCachePolicy noCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
    private string sURLGet = "";
    private string sPageGet = "prxyget.aspx";
    private string sURLPut = "";
    private string sPagePut = "prxyput.aspx";

    public override CommunicationsResponse BeginWorkerThreads()
    {
      if (ConnectionHub != null) { ConnectionHub.fConnectionOpenOnDemand = true; }
      if (fInitialized) return CommunicationsResponse.Success;
      try
      {
        ConnectionHub.BeginListening();
        ConnectionHub.BeginSendReceiveThreads();
        ConnectionHub.BeginProcessingThread();
      }
      catch (Exception ex) { throw new Exception("Error Starting Worker Threads", ex); }
      fInitialized = true;
      return CommunicationsResponse.Success;
    }

    /// <summary>
    /// Serializes and Broadcasts a screen update notification packet
    /// </summary>
    /// <param name="screenUpdateDataPacket"></param>
    /// <param name="nMaxLength"></param>
    /// <returns></returns>
    public override CommunicationsResponse BroadcastScreenUpdateNotification(
      ScreenUpdateDataPacket screenUpdateDataPacket, int nMaxLength
      )
    {
      CommunicationsResponse retVal = CommunicationsResponse.Success;
      if (screenUpdateDataPacket == null ||
          screenUpdateDataPacket.rgScreenUpdateData == null ||
          screenUpdateDataPacket.rgScreenUpdateData.Count == 0)
        return CommunicationsResponse.Success;

      if (ConnectionHub != null && outgoingConnection != null)
      {
        MemoryStream msOutgoingPacket = new MemoryStream(64000);
        BinaryWriter bw = new BinaryWriter(msOutgoingPacket);
        // 1 represents a screen update packet
        bw.Write((short)MessageTypes.ScreenUpdatePacket);
        bw.Write((short)Convert.ToInt16(screenUpdateDataPacket.rgScreenUpdateData.Count));
        int nCompLength = 0;
        for (int i = 0; i < screenUpdateDataPacket.rgScreenUpdateData.Count; i++)
        {
          ScreenUpdateData screenUpdateData =
            screenUpdateDataPacket.rgScreenUpdateData[i];

          // Compress the screen difference data
          // compress each packet individually
          screenUpdateData.SetByteContentType(ByteBufferContentType.CompressedDifferenceMap);

          bw.Write((short)Convert.ToInt16(screenUpdateData.Left));
          bw.Write((short)Convert.ToInt16(screenUpdateData.Top));
          bw.Write((short)Convert.ToInt16(screenUpdateData.Width));
          bw.Write((short)Convert.ToInt16(screenUpdateData.Height));
          // Note this is the compressed data length
          bw.Write((int)screenUpdateData.nDataLength);
          nCompLength += screenUpdateData.nDataLength;
          if (nCompLength > nMaxLength)
          {
            if (outgoingConnection.rgOutgoingBytes.Count > ConnectionHub.MessageQOut + 2 ||
              ConnectionHub.rgOutgoingBytes.Count > ConnectionHub.MessageQOut + 2)
            { return CommunicationsResponse.BroadcastQueueWasFull_SlowDown_And_Rebroadcast; }
            else { return CommunicationsResponse.BroadcastFullImageInstead; }
          }
        }

        for (int i = 0; i < screenUpdateDataPacket.rgScreenUpdateData.Count; i++)
        {
          ScreenUpdateData screenUpdateData =
            screenUpdateDataPacket.rgScreenUpdateData[i];

          bw.Write(screenUpdateData.byteData, 0, screenUpdateData.nDataLength);
        }
        bw.BaseStream.Seek(0, SeekOrigin.Begin);
        int srcOrigLen = (int)bw.BaseStream.Length;
        byte[] rgMessageToBroadcast =
          CommConnection.CreateCommandPacket(
            msOutgoingPacket.GetBuffer(),
            srcOrigLen
            );

        while (ConnectionHub.SendMessageOut(
          rgMessageToBroadcast,
          srcOrigLen + CommConnection.nPacketHeaderSize + CommConnection.nPacketLengthPrefixSize,
          CommConnection.eMessagePriority.Normal
        ) == false)
        {
          // too many notifications in queue!  Slow down!
          Application.DoEvents();
          Thread.Sleep(10);
          retVal = CommunicationsResponse.BroadcastQueueFull;
        }
        bw.Close();
      }

      return retVal;
    }

    public override CommunicationsResponse BroadcastScreenResolutionChangeOrZoom(int nWidth, int nHeight)
    {
      if (ConnectionHub != null)
      {
        using (MemoryStream msOutgoingPacket = new MemoryStream(256))
        {
          using (BinaryWriter bw = new BinaryWriter(msOutgoingPacket))
          {
            // 2 represents a screen size change
            bw.Write((short)MessageTypes.HostResolutionChange);
            bw.Write((short)Convert.ToInt16(nWidth));
            bw.Write((short)Convert.ToInt16(nHeight));
            bw.BaseStream.Seek(0, SeekOrigin.Begin);
            int srcOrigLen = (int)bw.BaseStream.Length;

            byte[] rgMessageToBroadcast =
              CommConnection.CreateCommandPacket(
                msOutgoingPacket.GetBuffer(),
                srcOrigLen);
            if (ConnectionHub != null) { ConnectionHub.EnqueueForSend(rgMessageToBroadcast); }
            bw.Close();
          }
        }
      }
      return CommunicationsResponse.Success;
    }

    /// <summary>
    /// For web communications, this routine simply sets up page URLs and creates a CommConnection
    /// to store queued incoming/outgoing data.
    /// </summary>
    /// <param name="sRemoteHost"></param>
    /// <param name="nPort"></param>
    /// <param name="Protocol"></param>
    /// <returns></returns>
    public override CommunicationsResponse EstablishOutgoingConnection(
      string sRemoteHost, int nPort, SupportedCommunicationsProtocol Protocol
      )
    {
      if (sRemoteHost == null && sRemoteHost.Length == 0) return CommunicationsResponse.Error;
      if (!sRemoteHost.EndsWith("/")) { sRemoteHost = sRemoteHost + "/"; }

      sURLGet = sRemoteHost + sPageGet;
      sURLPut = sRemoteHost + sPagePut;

      if (Protocol != SupportedCommunicationsProtocol.HTTP)
        return CommunicationsResponse.Error;

      outgoingConnection = new CommConnection(ConnectionHub, true, this);

      if (!fInitialized)
      {
        if (BeginWorkerThreads() == CommunicationsResponse.Success)
          fInitialized = true;
      }

      return CommunicationsResponse.Success;
    }

    public override CommunicationsResponse AcceptIncomingConnections(IPAddress localIP, int nPort) { return CommunicationsResponse.SupportedByP2POnly; }

    public WebCommunicationsLibrary()
    {
      Random rnd = new Random();
      ConnectionHub = new CommConnection(IPAddress.None, 4093);
      // Fill in some fake authentication data for now
      UserAuthenticationToken = "hello";
      UserName = "username";
      nGroupID = 1;
      UserIdentity = rnd.Next(2000000);
    }

    /// <summary>
    /// As its name suggests, this routine polls the remote web server for new, waiting data
    /// </summary>
    /// <returns></returns>
    public override CommunicationsResponse PollForNewDataFromServer()
    {
      if (!fInitialized) { Thread.Sleep(30); return CommunicationsResponse.NoDataAvailable; }
      CommunicationsResponse resp = CommunicationsResponse.Error;
      using (WebClient webConnectionGet = new WebClient())
      {
        try
        {
          webConnectionGet.QueryString = new System.Collections.Specialized.NameValueCollection(5);
          webConnectionGet.QueryString.Add("uid", nUserIdentity.ToString());
          webConnectionGet.QueryString.Add("sid", nGroupID.ToString());
          webConnectionGet.QueryString.Add("auth", sUserAuthenticationToken);
          webConnectionGet.QueryString.Add("uname", sUserName);
          webConnectionGet.QueryString.Add("ts", DateTime.Now.Ticks.ToString());
          // Handle this client's "Want Full Screen Refresh"
          if (fWantRefresh == true)
          {
            webConnectionGet.QueryString.Add("SENDFULLSCREEN", "1");
            fWantRefresh = false;
          }

          byte[] rgResponse = null;

          rgResponse = webConnectionGet.DownloadData(sURLGet);
          bool fSpecialRequest = false;

          if (rgResponse.Rank > 0 && rgResponse != null && rgResponse.Length > 0)
          {
            // Look for important messages in the response
            byte[] rgPresReq = System.Text.Encoding.ASCII.GetBytes("PRESENTATIONREQUEST");
            byte[] rgAccept = System.Text.Encoding.ASCII.GetBytes("ACCEPTED");
            byte[] rgReject = System.Text.Encoding.ASCII.GetBytes("REJECTED");

            string sResponseReadable = System.Text.Encoding.ASCII.GetString(rgResponse);
            if (sResponseReadable.IndexOf("PRESENTATIONREQUEST", StringComparison.OrdinalIgnoreCase) >= 0)
            {
              //MessageBox.Show("Presentation request.");
              resp = CommunicationsResponse.AskToPresent | CommunicationsResponse.Success;
              byte[] rgNew = new byte[rgResponse.Length - rgPresReq.Length];
              Array.Copy(rgResponse, rgPresReq.Length, rgNew, 0, rgResponse.Length - rgPresReq.Length);
              rgResponse = rgNew;
              fSpecialRequest = true;
              DialogResult res = MessageBox.Show(
                "The host has asked you to present.  Press YES to accept, or NO to decline.", "Presentation Request",
                MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1,
                MessageBoxOptions.DefaultDesktopOnly
                );
              if (res == DialogResult.Yes) { ConnectionHub.commInterfaceToUse.SetHostNegotiationStatus(HostNegotiationStatus.AcceptedHosting); }
              else if (res == DialogResult.No) { ConnectionHub.commInterfaceToUse.SetHostNegotiationStatus(HostNegotiationStatus.RefusedToHost); }
              else { ConnectionHub.commInterfaceToUse.SetHostNegotiationStatus(HostNegotiationStatus.None); }
            }
            else if (sResponseReadable.IndexOf("ACCEPTED", StringComparison.OrdinalIgnoreCase) >= 0)
            {
              // 
              resp = CommunicationsResponse.AcceptedPresent | CommunicationsResponse.Success; // 
              byte[] rgNew = new byte[rgResponse.Length - rgAccept.Length];
              Array.Copy(rgResponse, rgAccept.Length, rgNew, 0, rgResponse.Length - rgAccept.Length);
              rgResponse = rgNew;
              //hostRespStatus = HostNegotiationStatus.AcceptedHosting;
              fSpecialRequest = true;
              MessageBox.Show("The user accepted your request to host the presentation");
              fStopHosting = true;
            }
            else if (sResponseReadable.IndexOf("REJECTED", StringComparison.OrdinalIgnoreCase) >= 0)
            {
              resp = CommunicationsResponse.RejectedPresent | CommunicationsResponse.Success; // 
              byte[] rgNew = new byte[rgResponse.Length - rgReject.Length];
              Array.Copy(rgResponse, rgReject.Length, rgNew, 0, rgResponse.Length - rgReject.Length);
              rgResponse = rgNew;
              //hostRespStatus = HostNegotiationStatus.RefusedToHost;
              fSpecialRequest = true;
              MessageBox.Show("The user refused to take over the presentation");
            }

            ConnectionHub.AddBytesReceived(rgResponse.Length);
            Debug.Assert(outgoingConnection != null);
            if (fSpecialRequest)
            {
              lock (outgoingConnection.rgIncomingBytes)
              {
                outgoingConnection.rgIncomingBytes.Clear();
                if (outgoingConnection.msPartialRecv != null)
                {
                  outgoingConnection.msPartialRecv.SetLength(0);
                }
              }
            }
            else
            {
              lock (outgoingConnection.rgIncomingBytes)
              {
                outgoingConnection.rgIncomingBytes.Enqueue(rgResponse);
              }
            }
            resp = CommunicationsResponse.Success;
          }
          else
          {
            resp = CommunicationsResponse.NoDataAvailable;
          }
        }
        catch (Exception ex)
        {
          Debug.Assert(false, ex.ToString());
          Debug.WriteLine("Error reading updates from web proxy.");
          resp = CommunicationsResponse.Error;
        }
      }

      return resp;
    }

    /// <summary>
    /// Web communications library does not use prioritization and should not
    /// use BroadcastDataOut.  Please use BroadcastDataOutDirect
    /// </summary>
    /// <param name="rgBytesToSend"></param>
    /// <param name="nLen"></param>
    /// <returns></returns>
    public override CommunicationsResponse BroadcastDataOut(
      byte[] rgBytesToSend, int nLen
      )
    {
      Debug.Assert(false);
      MessageBox.Show("Invalid");
      return CommunicationsResponse.Error;
    }

    public static int nPutDataInProgress = 0;
    public static List<int> lockObj = new List<int>();

    public override CommunicationsResponse BroadcastDataOutDirect(
      byte[] rgBytesToSend, int nLen)
    {
      CommunicationsResponse ret = CommunicationsResponse.Success;
      if (!fInitialized) { BeginWorkerThreads(); }
      Debug.Assert(fInitialized);
      lock (lockObj)
      {
        StringBuilder sb = new StringBuilder(1024);
        sb.Append(sURLPut);
        sb.Append("?");
        sb.Append("uid=" + nUserIdentity.ToString());
        sb.Append("&");
        sb.Append("sid=" + nGroupID.ToString());
        sb.Append("&");
        sb.Append("auth=" + sUserAuthenticationToken);
        sb.Append("&");
        sb.Append("uname=" + sUserName);
        sb.Append("&");
        sb.Append("ts=" + DateTime.Now.Ticks.ToString());
        string sURLPutWithQueryParams = sb.ToString();
        string boundary = "----------" + DateTime.Now.Ticks.ToString("x");
        StringBuilder sbHeader = new StringBuilder(2048);
        sbHeader.Append("--");
        sbHeader.Append(boundary);
        sbHeader.Append("\r\nContent-Disposition: form-data; name=\"file\"; filename=\"data.bin\"");
        sbHeader.Append("\r\nContent-Type: application/octet-stream;\r\n\r\n");
        // Build the trailing boundary string as a byte array
        // ensuring the boundary appears on a line by itself
        byte[] boundaryBytes =
               Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
        string postHeader = sbHeader.ToString();
        byte[] postHeaderBytes = Encoding.UTF8.GetBytes(postHeader);
        long length = postHeaderBytes.Length + nLen + boundaryBytes.Length;
        try
        {
          HttpWebRequest wrPut = (HttpWebRequest)WebRequest.Create(sURLPutWithQueryParams);
          // wrPut.Pipelined = true;
          wrPut.ContentType = "multipart/form-data; boundary=" + boundary;
          wrPut.Method = "POST";
          wrPut.ContentLength = length;
          wrPut.CachePolicy = noCachePolicy;

          Stream requestStream = null;

          requestStream = wrPut.GetRequestStream();

          // Write out our post header
          requestStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);

          // Write our data to the request stream      
          requestStream.Write(rgBytesToSend, 0, nLen);

          // Write out the trailing boundary
          requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);

          // TODO: Make this configurable
          wrPut.Timeout = 50000;
          WebResponse response;
          response = wrPut.GetResponse();

          string sResponse = null;
          Stream s = null;
          StreamReader sr = null;

          s = response.GetResponseStream();
          sr = new StreamReader(s);
          sResponse = sr.ReadToEnd();
          if (sResponse != null && sResponse.Length > 1)
          {
            if (sResponse.StartsWith("FULLREFRESH"))
            {
              // The web server is requesting we resend a full screen
              // of data -- someone new has likely joined the group
              fNewConnection = true;
            }
            // Response should be "OK"
            else if (!sResponse.StartsWith("OK"))
            {
              Debug.Assert(false,
                "Response from putting data to the web server was " +
                sResponse);
            }

            ret = CommunicationsResponse.Success;
          }
        }
        catch (Exception ex)
        {
          Debug.WriteLine(ex.Message);
          ret = CommunicationsResponse.ServerNotAvailable;
        }
      }
      return ret;
    }
  }
}
